﻿using Common;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace CarPCBATest.Procedure
{
    #region 兼容ZLG的数据类型

    /*------------兼容ZLG的数据类型---------------------------------*/

    //1.ZLGCAN系列接口卡信息的数据类型。
    public struct VCI_BOARD_INFO
    {
        public UInt16 hw_Version;
        public UInt16 fw_Version;
        public UInt16 dr_Version;
        public UInt16 in_Version;
        public UInt16 irq_Num;
        public byte can_Num;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] str_Serial_Num;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
        public byte[] str_hw_Type;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public byte[] Reserved;
    }

    /////////////////////////////////////////////////////
    //2.定义CAN信息帧的数据类型。
    public unsafe struct VCI_CAN_OBJ  //使用不安全代码
    {
        public uint ID;
        public uint TimeStamp;        //时间标识
        public byte TimeFlag;         //是否使用时间标识
        public byte SendType;         //发送标志。保留，未用
        public byte RemoteFlag;       //是否是远程帧
        public byte ExternFlag;       //是否是扩展帧
        public byte DataLen;          //数据长度
        public fixed byte Data[8];    //数据
        public fixed byte Reserved[3];//保留位
    }

    //3.定义初始化CAN的数据类型
    public struct VCI_INIT_CONFIG
    {
        public UInt32 AccCode;
        public UInt32 AccMask;
        public UInt32 Reserved;
        public byte Filter;   //0或1接收所有帧。2标准帧滤波，3是扩展帧滤波。
        public byte Timing0;  //波特率参数，具体配置，请查看二次开发库函数说明书。
        public byte Timing1;
        public byte Mode;     //模式，0表示正常模式，1表示只听模式,2自测模式
    }

    /*------------其他数据结构描述---------------------------------*/

    //4.USB-CAN总线适配器板卡信息的数据类型1，该类型为VCI_FindUsbDevice函数的返回参数。
    public struct VCI_BOARD_INFO1
    {
        public UInt16 hw_Version;
        public UInt16 fw_Version;
        public UInt16 dr_Version;
        public UInt16 in_Version;
        public UInt16 irq_Num;
        public byte can_Num;
        public byte Reserved;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] str_Serial_Num;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] str_hw_Type;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] str_Usb_Serial;
    }

    /*------------数据结构描述完成---------------------------------*/

    #endregion 兼容ZLG的数据类型

    internal class CanMessageBuffer
    {
        const int TIMEOUT_MS = 500;
        private readonly VCI_CAN_OBJ[] _buffer;

        // Where the next message will be put
        private int _putIndex;

        // Where the next message will be get (only if != putIndex)
        private int _getIndex;

        private bool _busy;
        private int _timeoutWait;

        public bool IsEmpty => (_putIndex == _getIndex);

        public uint Channel { get; set; }
        public uint ID { get; set; }

        public CanMessageBuffer(int bufferSize)
        {
            _buffer = new VCI_CAN_OBJ[bufferSize];
            _getIndex = _putIndex = 0;
        }

        public CanMessageBuffer(int bufferSize, uint channel, uint id)
        {
            _buffer = new VCI_CAN_OBJ[bufferSize];
            _getIndex = _putIndex = 0;
            Channel = channel;
            ID = id;
        }

        public unsafe void AddMessage(VCI_CAN_OBJ msg)
        {
            WaitSemaphore();
            _busy = true;
            _buffer[_putIndex++] = msg;
            if (_putIndex == _buffer.Length) _putIndex = 0; //buffer 满了溢出
            if (_putIndex == _getIndex)
            {
                _getIndex++;
                if (_getIndex == _buffer.Length) _getIndex = 0;
            }

            #region 写日志

            string str = $"通道[{Channel}] 接收到数据: ";
            str += "  帧ID:0x" + System.Convert.ToString(msg.ID, 16);
            str += "  帧格式:";
            if (msg.RemoteFlag == 0)
                str += "数据帧 ";
            else
                str += "远程帧 ";
            if (msg.ExternFlag == 0)
                str += "标准帧 ";
            else
                str += "扩展帧 ";
            str += $"时间戳: {((double)msg.TimeStamp) / 10}ms ";

            int len = msg.DataLen;
            byte[] ret = new byte[len];
            for (int j = 0; j < len; j++)
            {
                ret[j] = msg.Data[j];
            }
            str += "数据:" + BitConverter.ToString(ret).Replace("-", " ");

            //Mylog.Debug(str);

            #endregion 写日志

            _busy = false;
        }

        public VCI_CAN_OBJ[] GetLastMessages(int num)
        {
            WaitSemaphore();
            _busy = true;

            VCI_CAN_OBJ[] msgs;
            if (_putIndex == _getIndex)
            {
                // No new messages
                msgs = new VCI_CAN_OBJ[0];
            }
            else if (_putIndex > _getIndex || _putIndex >= num)
            {
                // New messages in a single block
                var size = (_putIndex > _getIndex) ? Math.Min(_putIndex - _getIndex, num) : num;
                msgs = new VCI_CAN_OBJ[size];
                Array.Copy(_buffer, _putIndex - size, msgs, 0, size);
                _getIndex = _putIndex;
            }
            else
            {
                // New messages in two blocks (putIndex < getIndex)
                // Block1: ??? --> last index, Block2: 0 --> (putIndex - 1)
                var sizeBlock1 = Math.Min(_buffer.Length - _getIndex, num - _putIndex);
                msgs = new VCI_CAN_OBJ[sizeBlock1 + _putIndex];
                Array.Copy(_buffer, _buffer.Length - sizeBlock1, msgs, 0, sizeBlock1);
                Array.Copy(_buffer, 0, msgs, sizeBlock1, _putIndex);
                _getIndex = _putIndex;
            }
            _busy = false;
            return msgs;
        }

        public void Clear()
        {
            WaitSemaphore();
            _getIndex = _putIndex = 0;
        }

        private void WaitSemaphore()
        {
            if (!_busy) return;
            while (_busy && _timeoutWait < TIMEOUT_MS)
            {
                Thread.Sleep(10);
                _timeoutWait += 10;
            }
            if (_timeoutWait >= TIMEOUT_MS) throw new Exception("Operation timed out");
            _timeoutWait = 0;
        }
    }

    internal class CanControl
    {
        #region 兼容ZLG的函数描述

        private const int DEV_USBCAN = 3;
        private const int DEV_USBCAN2 = 4;
        /// <summary>
        /// </summary>
        /// <param name="DeviceType"> </param>
        /// <param name="DeviceInd">  </param>
        /// <param name="Reserved">   </param>
        /// <returns> </returns>
        /*------------兼容ZLG的函数描述---------------------------------*/

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_OpenDevice(UInt32 DeviceType, UInt32 DeviceInd, UInt32 Reserved);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_CloseDevice(UInt32 DeviceType, UInt32 DeviceInd);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_InitCAN(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd, ref VCI_INIT_CONFIG pInitConfig);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_ReadBoardInfo(UInt32 DeviceType, UInt32 DeviceInd, ref VCI_BOARD_INFO pInfo);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_GetReceiveNum(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_ClearBuffer(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_StartCAN(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_ResetCAN(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_Transmit(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd, ref VCI_CAN_OBJ pSend, UInt32 Len);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_Receive(UInt32 DeviceType, UInt32 DeviceInd, UInt32 CANInd, ref VCI_CAN_OBJ pReceive, UInt32 Len, Int32 WaitTime);

        /*------------其他函数描述---------------------------------*/

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_ConnectDevice(UInt32 DevType, UInt32 DevIndex);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_UsbDeviceReset(UInt32 DevType, UInt32 DevIndex, UInt32 Reserved);

        [DllImport("controlcan.dll")]
        private static extern UInt32 VCI_FindUsbDevice(ref VCI_BOARD_INFO1 pInfo);

        /*------------函数描述结束---------------------------------*/

        private const UInt32 m_devtype = 4;//USBCAN2

        private static UInt32 m_bOpen = 0;
        private const UInt32 m_devind = 0;

        #endregion 兼容ZLG的函数描述

        /// <summary>
        /// 连接设备
        /// </summary>
        /// <param name="canId"> can通道id </param>
        [Conditional("DEVICE")]
        public static void Connect()
        {
            if (m_bOpen == 1)
            {
                VCI_CloseDevice(m_devtype, m_devind);
                m_bOpen = 0;
            }
            Thread.Sleep(100);
            if (VCI_OpenDevice(m_devtype, m_devind, 0) == 0)
            {
                throw new Exception("打开Can设备失败,请检查设备类型和设备索引号是否正确");
            }
            m_bOpen = 1;
        }

        [Conditional("DEVICE")]
        public static void Init(uint canId, uint canBaud)
        {
            VCI_INIT_CONFIG config = new VCI_INIT_CONFIG();
            config.AccCode = 0x00;
            config.AccMask = 0xFFFFFFFF;

            config.Timing1 = 0x1C;
            if (canBaud == 250)
            {
                config.Timing0 = 0x01;
            }
            else if (canBaud == 500)
            {
                config.Timing0 = 0x00;
            }
            else
            {
                throw new Exception($"错误的波特率[{canBaud}]");
            }
            config.Filter = 0x01;
            config.Mode = 0x00;
            VCI_InitCAN(m_devtype, m_devind, canId, ref config);
        }

        static List<CanMessageBuffer> CanMessageManager = new List<CanMessageBuffer>();

        private static CanMessageBuffer GetBuffer(uint canId, uint messageId)
        {
            var buffer = CanMessageManager.Where(x => x.Channel == canId && x.ID == messageId);
            if (buffer.Count() > 0)
            {
                return buffer.First();
            }
            else
            {
                var buf = new CanMessageBuffer(2000, canId, messageId);
                CanMessageManager.Add(buf);
                return buf;
            }
        }

        [Conditional("DEVICE")]
        public static void StartCan(uint canId)
        {
            if (m_bOpen == 0)
                return;
            VCI_StartCAN(m_devtype, m_devind, canId);
        }

        [Conditional("DEVICE")]
        public static void ClearBuffer(uint canId)
        {
            if (m_bOpen == 0)
                return;
            VCI_ClearBuffer(m_devtype, m_devind, canId);
        }

        public static void StopCan(uint canId)
        {
            if (m_bOpen == 0)
                return;
            VCI_ResetCAN(m_devtype, m_devind, canId);
        }

        public static unsafe byte[] ParseData(VCI_CAN_OBJ message)
        {
            int len = message.DataLen;
            byte[] ret = new byte[len];
            for (int i = 0; i < len; i++)
            {
                ret[i] = message.Data[i];
            }
            return ret;
        }

        public static unsafe void ReadDataFromUsb(uint canId)
        {
            VCI_CAN_OBJ[] tmpData = new VCI_CAN_OBJ[2000];
            lock (locker)
            {
                var res = VCI_Receive(m_devtype, m_devind, canId, ref tmpData[0], 2000, 100);
                for (int i = 0; i < res; i++)
                {
                    var id = tmpData[i].ID;
                    var buffer = GetBuffer(canId, id);
                    buffer.AddMessage(tmpData[i]);
                }
                Mylog.Info($"CanReadCount={res}");
            }
        }

        public static VCI_CAN_OBJ[] GetLastMessages(uint canId, uint messageId, int count)
        {
            var buffer = GetBuffer(canId, messageId);
            return buffer.GetLastMessages(count);
        }

        public static byte[] GetLastOneMessage(uint canId, uint messageId)
        {
            var buffer = GetBuffer(canId, messageId);
            var messages = buffer.GetLastMessages(1);
            if (messages.Length == 0)
            {
                throw new Exception("CAN通信异常");
            }
            return ParseData(messages[0]);
        }

        private static readonly object locker = new object();

        /// </summary> <param name="canId">通道号 0 1</param> <param
        /// name="id">帧命令</param> <param name="externFlag">0 标准帧 1扩展帧</param>
        /// <param name="strdata">数据</param>
        [Conditional("DEVICE")]
        public static unsafe void SendCmd(uint canId, uint id, string strdata, byte externFlag = 0)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            lock (locker)
            {
                if (m_bOpen == 0)
                    return;

                VCI_CAN_OBJ sendobj = new VCI_CAN_OBJ();
                //sendobj.Init();
                sendobj.RemoteFlag = 0x00; //都是数据帧
                sendobj.ExternFlag = externFlag;
                sendobj.ID = id;
                int len = 8;
                sendobj.DataLen = System.Convert.ToByte(len);
                //String strdata = textBox_Data.Text;
                int i = -1;
                if (i++ < len - 1)
                    sendobj.Data[0] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[1] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[2] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[3] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[4] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[5] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[6] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                if (i++ < len - 1)
                    sendobj.Data[7] = System.Convert.ToByte("0x" + strdata.Substring(i * 3, 2), 16);
                try
                {
                    //Mylog.Debug("SendCmd耗时0：" + sw.ElapsedMilliseconds.ToString());
                    if (VCI_Transmit(m_devtype, m_devind, canId, ref sendobj, 1) == 0)
                    {
                        Mylog.Error($"can发送失败[{id.ToString("x16")}], cmd[{strdata}]");
                        Thread.Sleep(12);
                    }
                    else
                    {
                        //Thread.Sleep(3); // 发送间隔最小为5
                    }
                    //Mylog.Debug($"{DateTime.Now.ToString("mm\\:ss.fff")}发送数据[{sendCont++}]-通道[{canId}] ID[{id.ToString("X16")}] data[{strdata}]");
                    //Mylog.Debug("SendCmd耗时1：" + sw.ElapsedMilliseconds.ToString());
                }
                catch (Exception ex)
                {
                    Mylog.Error(ex, ex.Message);
                    Mylog.Error($"canId[{canId}] id[{id}]   data[{strdata}]");
                }
            }
        }

        [Conditional("DEVICE")]
        public static void Close()
        {
            if (m_bOpen == 1)
            {
                VCI_CloseDevice(m_devtype, m_devind);
            }
        }
    }

    public class Rcm
    {
        public static uint CRC_Cal(uint dataID, byte Cntr, byte lampVal)
        {
            byte j;
            byte crc;

            crc = 0;

            crc ^= ((byte)(dataID % 256));
            for (j = 0; j < 8; j++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }
            crc ^= ((byte)(dataID / 256));
            for (j = 0; j < 8; j++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }
            crc ^= Cntr;
            for (j = 0; j < 8; j++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }
            crc ^= lampVal;
            for (j = 0; j < 8; j++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }

            crc ^= 0x00;
            return crc;
        }

        public static byte CRCCal(uint dataID, byte cntr, byte lampVal)
        {
            byte crc = 0x00;
            crc ^= (byte)(dataID % 256);
            for (int i = 0; i < 8; i++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }
            crc ^= (byte)(dataID % 256);
            for (int i = 0; i < 8; i++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }
            crc ^= cntr;
            for (int i = 0; i < 8; i++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }
            crc ^= lampVal;
            for (int i = 0; i < 8; i++)
            {
                /*check if MSB is 1*/
                if ((crc & 0x80) > 0x0)
                {
                    crc <<= 1;
                    crc ^= 0x1D;
                }
                else
                {
                    crc <<= 1;
                }
            }

            crc ^= 0x00;
            return crc;
        }
    }
}